﻿//=====================================================================================================
// LocalizationLibrary
// The Localization Library is a collection of reusable software components that provide 
// support for localization. This library enables you to localize WPF, Silverlight and WP7 applications.
// (http://localizationlibrary.codeplex.com)
//=====================================================================================================
// Author: Nicola Gallo (Website: www.nicola.gallo.name, Email: nicolagmt@hotmail.it) 
//=====================================================================================================
using ReflectionStudio.Core.Reflection;
namespace ReflectionStudio.LocalizationLibrary
{
    //.NET References
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Threading;
    using ReflectionStudio.LocalizationLibrary.Attributes;
    using LocalizationLibrary.Messaging;
    using ReflectionStudio.Core.Events;
    /// <summary>
    /// Defines the application's localization manager.
    /// </summary>
    public partial class LocalizationManager
    {
        //Events

        /// <summary>
        /// Occurs when the current language is changed.
        /// </summary>
        public static event LanguageChangedEventHandler LanguageChanged;

        /// <summary>
        /// Occurs when the <see cref="LocalizationManager"/> is reset.
        /// </summary>
        public static event EventHandler OnReset;

        //Fields

        private static volatile Language _currentLanguage;
        private static Object _localizationManagerLock = new object();
        private static Type _appLocalizationProviderType;
        private static LocalizationEngineInfo _engineInfo;

        //Constructors

        /// <summary>
        /// Static constructor.
        /// </summary>
        static LocalizationManager()
        {
            LocalizationManager.RegisteredAssemblies = new Collection<Assembly>();
            LocalizationManager.RegisteredLocalizationModels = new Collection<Type>();
            LocalizationManager.LocalizationModelsTable = new LocalizationModelsTable();
            LocalizationManager.StaticResourcesTable = new StaticResourcesTable();
            LocalizationManager.DynamicResourcesTable = new DynamicResourcesTable();
            LocalizationManager.MaxWaitingMillisecondForLocalization = 3000;
            LocalizationManager.InitializeTaskManager();
            /*AssemblyManager.AssemblyLoaded += (s, e) => 
                {
                    LocalizationManager.RegisterAssembly(e.Assembly);
                };
             * */
        }

        //Readonly properties

        /// <summary>
        /// Gets the registered assemblies.
        /// </summary>
        private readonly static Collection<Assembly> RegisteredAssemblies;

        /// <summary>
        /// Gets the registered localization models' types.
        /// </summary>
        private readonly static Collection<Type> RegisteredLocalizationModels;

        /// <summary>
        /// Gets the localization models' table.
        /// </summary>
        private readonly static LocalizationModelsTable LocalizationModelsTable;

        /// <summary>
        /// Gets the dynamic resources' table.
        /// </summary>
        private readonly static DynamicResourcesTable DynamicResourcesTable;

        /// <summary>
        /// Gets static resources table.
        /// </summary>
        private readonly static StaticResourcesTable StaticResourcesTable;
        
        //Properties

        /// <summary>
        /// Returns <value>true</value> whether the localization manager is initialized, <value>false</value> otherwise.
        /// </summary>
        public static bool IsInitialized { get; private set; }

        /// <summary>
        /// Gets the current language.
        /// </summary>
        public static Language CurrentLanguage
        {
            get
            {
                return LocalizationManager._currentLanguage;
            }
            set
            {
                LocalizationManager.ChangeCurrentLanguage(value);
            }
        }

        /// <summary>
        /// Gets/Sets the default type used for localization.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// Thrown whether either the input value is null or doesn't inherits from the class LocalizationProvider.
        /// </exception>
        public static Type AppLocalizationProviderType 
        {
            get
            {
                return LocalizationManager._appLocalizationProviderType;
            }
            set 
            {
                if (value == null)
                {
                    throw new InvalidOperationException("You can't assign null.");
                }
                if ((value != null) && (!typeof(LocalizationProvider).IsAssignableFrom(value)))
                {
                    throw new InvalidOperationException("You have to assign a type that inherits from the class LocalizationProvider.");
                }
                LocalizationManager._appLocalizationProviderType = value;
            }
        }

        /// <summary>
        /// Gets/Sets the <see cref="LanguagesProvider"/> that provides all the available languages for the application.
        /// </summary>
        public static LanguagesProvider AppLanguagesProvider { get; set; }

        /// <summary>
        /// Gets informations about the localization engine.
        /// </summary>
        public static LocalizationEngineInfo EngineInfo
        {
            get
            {
                if (LocalizationManager._engineInfo == null)
                {
                    LocalizationManager._engineInfo = new LocalizationEngineInfo();
                }
                return LocalizationManager._engineInfo;
            }
        }

        /// <summary>
        /// Sets the maximum waiting millisecond for localization.
        /// </summary>
        public static double MaxWaitingMillisecondForLocalization { get; set; }
 
        /// <summary>
        /// Gets True if is in design time, False otherwise.
        /// </summary>
        internal static bool IsDesignTime
        { 
            get 
            { 
#if SILVERLIGHT
                bool retVal = false;
                if (Application.Current.RootVisual != null)
                {
                    retVal = DesignerProperties.GetIsInDesignMode(Application.Current.RootVisual);
                }
                return retVal;
#else
                bool retVal = false;
                if (System.Windows.Application.Current != null)
                {
                    retVal = DesignerProperties.GetIsInDesignMode(System.Windows.Application.Current.MainWindow);
                }
                return retVal;
#endif
            } 
        }

        //Methods

        /// <summary>
        /// Initializes the localization manager.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// Thrwon whether the property LocalizationProviderType is null.
        /// </exception>
        public static void Initialize()
        {
            if (!LocalizationManager.IsInitialized)
            {
                if (LocalizationManager.AppLocalizationProviderType == null)
                {
                    throw new InvalidOperationException("You have to assign the property LocalizationProviderType.");
                }
                lock (LocalizationManager._localizationManagerLock)
                {
                    if (LocalizationManager.CurrentLanguage == null)
                    {
                        LocalizationManager.ChangeCurrentLanguage((Language)"en");
                    }
                    LocalizationManager.IsInitialized = true;
                    /*
                    List<Assembly> assemblies = new List<Assembly>();
  
                    assemblies.AddRange(AssemblyManager.GetCurrentDomainAssemblies()); 
                    foreach (Assembly assembly in assemblies)
                    {
                        LocalizationManager.RegisterAssembly(assembly);
                    }
                    ConsoleExtended.WriteLine("LocalizationManager has been initialized.");
                    */
                }
            }
        }

        /// <summary>
        /// Resets the <see cref="LocalizationManager"/>.
        /// </summary>
        public static void Reset() 
        {
            lock (LocalizationManager.LocalizationWorker)
                lock (LocalizationManager.RegisteredAssemblies)
                    lock (LocalizationManager.RegisteredLocalizationModels)
                        lock (LocalizationManager.LocalizationModelsTable)
                            lock (LocalizationManager.StaticResourcesTable)
                                lock (LocalizationManager.DynamicResourcesTable)

            {
                LocalizationManager.LocalizationWorker.Reset();
                LocalizationManager.RegisteredAssemblies.Clear();
                LocalizationManager.RegisteredLocalizationModels.Clear();
                LocalizationManager.LocalizationModelsTable.Reset();
                LocalizationManager.StaticResourcesTable.Reset();
                LocalizationManager.DynamicResourcesTable.Reset();
            }
            //AssemblyManager.Reset();
            if (LocalizationManager.OnReset != null)
            {
                LocalizationManager.OnReset("LocalizationManager", EventArgs.Empty);
            }
        }

        /// <summary>
        /// Changes the current language.
        /// </summary>
        /// <param name="language">Is the new language.</param>
        /// <exception cref="ArgumentNullException">
        /// Thrown whether <paramref name="language"/> parameter is null.
        /// </exception>
        /// If there is a existing localization whether the <paramref name="waitForExistingLocalization"/> is equals to true then expects it terms otherwise kill it. 
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Thrown whether <paramref name="language"/> parameter is null.
        /// </exception>
        private static void ChangeCurrentLanguage(Language language)
        {
            if (language == null)
            {
                throw new ArgumentNullException("language parameter shouldn't be null.");
            }
            if (LocalizationManager._currentLanguage != language)
            {
                lock (LocalizationManager._localizationManagerLock)
                {
                    Language oldLanguage = LocalizationManager._currentLanguage;
                    LocalizationManager._currentLanguage = language;
                    LocalizationManager.EnsureCurrentThreadCulture();
                    String message = String.Format("The new current language is {0}.", LocalizationManager.CurrentLanguage.Code);
                    //ConsoleExtended.WriteLine(message);
                    //Tracer.Verbose("LocalizationManager.ChangeCurrentLanguage", message);
                    LanguageChangedEventHandler handler = LocalizationManager.LanguageChanged;
                    if (handler != null)
                    {
                        handler(null, new LanguageChangedEventArgs(language, oldLanguage));
                    }
                    MessagingManager.Publish(new LanguageMessage(language));
                }
            }
        }

        /// <summary>
        /// Ensures the current thread's culture.
        /// </summary>
        internal static void EnsureCurrentThreadCulture() 
        {
            CultureInfo culture = new CultureInfo(LocalizationManager._currentLanguage.Code);
            if (Thread.CurrentThread.CurrentCulture != culture)
            {
                Thread.CurrentThread.CurrentCulture = culture;
            }
            if (Thread.CurrentThread.CurrentUICulture != culture)
            {
                Thread.CurrentThread.CurrentUICulture = culture;
            }
        }

        /// <summary>
        /// Registers the input <paramref name="assembly"/>.
        /// </summary>
        /// <param name="assembly">Assembly to register.</param>
        /// <exception cref="InvalidOperationException">
        /// Thrown whether the <see cref="LocalizationManger"/> isn't initialized.
        /// </exception>
        public static void RegisterAssembly(Assembly assembly)
        {
 
            if (!LocalizationManager.IsInitialized)
            {
                throw new InvalidOperationException("You can't register an assembly if the LocalizationManager isn't initialized.");
            }
            
            if (assembly != null)
            {
                List<DynamicResource> dynamicResourcesProcessed = new List<DynamicResource>();
                lock (LocalizationManager._localizationManagerLock)
                    lock (AssemblyManager.ReflectionCache)
                        lock (LocalizationManager.DynamicResourcesTable)
                        {
                            bool assemblyRegistered = false;
                            lock (LocalizationManager.RegisteredAssemblies)
                            {
                                assemblyRegistered = LocalizationManager.RegisteredAssemblies.Contains(assembly);
                            }
                            if (!assemblyRegistered)
                            {
                                lock (LocalizationManager.RegisteredAssemblies)
                                {
                                    LocalizationManager.RegisteredAssemblies.Add(assembly);
                                }
                                AssemblyRef assemblyRef = AssemblyManager.ReflectionCache.LoadAssembly(assembly);
                                if (assemblyRef.ResourceLocalizedAttribute.Count() == 0)
                                    return;
                                foreach (DynamicResourcePartAttribute resourceLocAttrib in assemblyRef.ResourceLocalizedAttribute)
                                {
                                    DynamicResource dynamicResource = LocalizationManager.DynamicResourcesTable[resourceLocAttrib.ResourceID];
                                    if (dynamicResource == null)
                                    {
                                        dynamicResource = new DynamicResource(resourceLocAttrib.ResourceID);
                                        LocalizationManager.DynamicResourcesTable.Add(dynamicResource, false);
                                    }
                                    dynamicResource.Add((Language)resourceLocAttrib.Language,
                                        resourceLocAttrib.Value, resourceLocAttrib.Policy, false);
                                    if (!dynamicResourcesProcessed.Contains(dynamicResource))
                                    {
                                        dynamicResourcesProcessed.Add(dynamicResource);
                                    }
                                }
                            }
                        }
                foreach (DynamicResource dynamicResource in dynamicResourcesProcessed)
                {
                    MessagingManager.Publish(new ResourceMessage(dynamicResource, Operation.Processed));
                }
                String message = String.Format("{0} has been processed.", assembly.FullName);
                Tracer.Verbose("LocalizationManager.RegisterAssembly", message);
            } 
            
        }

        /// <summary>
        /// Registers the input <paramref name="localizationModel"/> into localization model table.
        /// </summary>
        /// <param name="localizationModel"></param>
        /// <returns>True if the input <paramref name="localizationModelRef"/> is registered, False otherwise.</returns>
        /// <exception cref="InvalidOperationException">
        /// Thrown whether the <see cref="LocalizationManger"/> isn't initialized.
        /// </exception>
        public static bool RegisterLocalizationModel(Object localizationModel)
        {
            bool retVal = false;
       
            if (LocalizationManager.IsDesignTime)
            {
                return true;
            }
            if (!LocalizationManager.IsInitialized)
            {
                throw new InvalidOperationException("You can't register a localization model if the LocalizationManager isn't initialized.");
            }
           
            if (localizationModel != null)
            {
                List<DynamicResource> dynamicResourcesProcessed = new List<DynamicResource>();
                lock (LocalizationManager._localizationManagerLock)
                    lock (AssemblyManager.ReflectionCache)
                        lock (LocalizationManager.DynamicResourcesTable)
                            lock (LocalizationManager.LocalizationModelsTable)
                            {
                                bool typeRegistered = false;
                                lock (LocalizationManager.RegisteredLocalizationModels)
                                {
                                    typeRegistered = LocalizationManager.RegisteredLocalizationModels.Contains(localizationModel.GetType());
                                }
                                if (!typeRegistered)
                                {
                                    lock (LocalizationManager.RegisteredLocalizationModels)
                                    {
                                        LocalizationManager.RegisteredLocalizationModels.Add(localizationModel.GetType());
                                    }
                                    TypeRef typeRef = AssemblyManager.ReflectionCache.LoadType(localizationModel.GetType());
                                    PropertyInfoRef[] propertyInfosRef = typeRef.GetProperties();
                                    foreach (PropertyInfoRef propertyInfoRef in propertyInfosRef)
                                    {
                                        Attribute[] attribs = propertyInfoRef.GetAttributes<ILocalizationAttribute>();
                                        if (attribs.Length > 0)
                                        {
                                            String resourceID = null;
                                            Queue<LocalizeDynamicallyAttribute> localizationDynAttQueue = new Queue<LocalizeDynamicallyAttribute>();
                                            bool? isStaticResource = new Nullable<bool>();
                                            foreach (ILocalizationAttribute attrib in attribs)
                                            {
                                                if (attrib is LocalizeAttribute)
                                                {
                                                    if (isStaticResource.HasValue)
                                                    {
                                                        throw new InvalidOperationException("Invalid operation occurs because of a wrong definition of the localization attribute. You can register only one static resources for each property");
                                                    }
                                                    LocalizeAttribute localizeAttrib = attrib as LocalizeAttribute;
                                                    StaticResource staticResource = new StaticResource(localizeAttrib.Key, localizeAttrib.ResourceType);
                                                    LocalizationManager.StaticResourcesTable.Add(staticResource);
                                                    propertyInfoRef.Resource = staticResource;
                                                }
                                                else if (attrib is LocalizeDynamicallyAttribute)
                                                {
                                                    if ((isStaticResource.HasValue) && (isStaticResource.Value))
                                                    {
                                                        throw new InvalidOperationException("Invalid operation occurs because of a wrong definition of the localization attribute .");
                                                    }
                                                    LocalizeDynamicallyAttribute localizationAttribute = attrib as LocalizeDynamicallyAttribute;
                                                    if (!String.IsNullOrEmpty(localizationAttribute.ResourceID))
                                                    {
                                                        if ((!String.IsNullOrEmpty(resourceID)) && (!resourceID.Equals(localizationAttribute.ResourceID)))
                                                        {
                                                            throw new InvalidOperationException("Invalid operation occurs because of a wrong definition of the localization attribute .");
                                                        }
                                                        resourceID = localizationAttribute.ResourceID;
                                                    }
                                                    localizationDynAttQueue.Enqueue(localizationAttribute);
                                                }
                                            }
                                            while (localizationDynAttQueue.Count != 0)
                                            {
                                                if (String.IsNullOrEmpty(resourceID))
                                                {
                                                    resourceID = Guid.NewGuid().ToString();
                                                }
                                                LocalizeDynamicallyAttribute localizationAttribute = localizationDynAttQueue.Dequeue();
                                                DynamicResource dynamicResource = LocalizationManager.DynamicResourcesTable[resourceID];
                                                if (dynamicResource == null)
                                                {
                                                    dynamicResource = new DynamicResource(resourceID);
                                                    LocalizationManager.DynamicResourcesTable.Add(dynamicResource, false);
                                                }
                                                propertyInfoRef.Resource = dynamicResource;
                                                if ((localizationAttribute.Language != null) && (localizationAttribute.Value != null))
                                                {
                                                    dynamicResource.Add((Language)localizationAttribute.Language, localizationAttribute.Value, 
                                                        localizationAttribute.Policy , false);
                                                }
                                                if (!dynamicResourcesProcessed.Contains(dynamicResource))
                                                {
                                                    dynamicResourcesProcessed.Add(dynamicResource);
                                                }
                                            }
                                        }
                                    }
                                    retVal = true;
                                }
                                LocalizationModelRef localizationModelRef = new LocalizationModelRef(localizationModel);
                                LocalizationManager.LocalizationModelsTable.AddLocalizationModelRef(localizationModelRef);
                            }
                foreach (DynamicResource dynamicResource in dynamicResourcesProcessed)
                {
                    MessagingManager.Publish(new ResourceMessage(dynamicResource, Operation.Processed));
                }
                String message = String.Format("The localization model {0} has been processed.", localizationModel.GetType().FullName);
                //Tracer.Verbose("LocalizationManager.RegisterAssembly", message);
                MessagingManager.Publish(new LocalizationModelMessage(localizationModel, Operation.Processed));
            } 
            return retVal;
        }

        /// <summary>
        /// Getsa <see cref="DynamicResourceDescription"/> for the input resourceID.
        /// </summary>
        /// <param name="resourceID">Resource identifier.</param>
        /// <returns></returns>
        public static DynamicResourceDescription GetResourceDescription(String resourceID) 
        {
            DynamicResourceDescription dRDescription = null;
            if (!String.IsNullOrEmpty(resourceID))
            {
                DynamicResource dynamicResource = null;
                lock (LocalizationManager.DynamicResourcesTable)
                {
                    dynamicResource = LocalizationManager.DynamicResourcesTable[resourceID];
                }
                if (dynamicResource != null)
                {
                    dRDescription = new DynamicResourceDescription(dynamicResource);
                }
            }
            return dRDescription;
        }
    }
}